import numpy as np

from utils.features.prepare_for_training import prepare_for_training


# from utils.features import prepare_for_training


class LinearRegression:
    def __init__(self, data, labels, polynomial_degree=0, sinusoid_degree=0, normalize_data=True):
        """
        1. 对数据进行预处理
        2. 得到所有的特征个数
        3. 初始化参数矩阵
        """
        (data_processed,
         features_mean,
         features_deviation) = prepare_for_training(data, polynomial_degree, sinusoid_degree, normalize_data)

        self.data = data_processed
        self.labels = labels
        self.feature_mean = features_mean
        self.feature_deviation = features_deviation
        self.polynomial_degree=polynomial_degree
        self.sinusoid_degree=sinusoid_degree
        self.normalize_data=normalize_data

        num_features = self.data.shape[1]
        self.theta = np.zeros((num_features,1))

    def train(self,alpha,num_iterations = 500):
        """
        训练模块，执行梯度下降
        :param alpha: 学习率
        :param num_iterations: 迭代次数
        :return:
        """
        cost_history = self.gradient_descent(alpha,num_iterations)
        return self.theta, cost_history

    def gradient_descent(self,alpha,num_iterations):
        """
        实际迭代模块，损失计算，会迭代 num_iterations 次
        :param alpha:
        :param num_iterations:
        :return:
        """
        cost_history = [] # 损失值列表
        for _ in range(num_iterations):
            self.gradient_step(alpha) # 完成一次更新
            cost_history.append(self.cost_function(self.data,self.labels))  # 计算损失
        return cost_history

    # 梯度下降
    def gradient_step(self,alpha):# alpha 学习率
        """
        梯度下降，参数更新方法，注意是矩阵计算
        :param alpha: 学习率
        :return:
        """
        num_examples = self.data.shape[0] #样本个数
        prediction = LinearRegression.hypothesis(self.data,self.theta) #计算预测值
        delta = prediction - self.labels # 预测值减去真实值得到参差
        theta = self.theta
        theta = theta - alpha * (1 / num_examples) * (np.dot(delta.T, self.data)).T  # .T 转置  更新参数
        self.theta = theta

    #计算预测值
    @staticmethod   # 静态函数
    def hypothesis(data,theta):
        predictions = np.dot(data,theta)    # 获取预测值
        return predictions


    def cost_function(self, data, labels):
        """
        损失计算方法
        :param data:
        :param labels:
        :return:
        """
        num_examples = data.shape[0]
        delta = LinearRegression.hypothesis(self.data,self.theta) - labels
        cost = (1/2)*np.dot(delta.T,delta)
        return cost[0][0]

    def get_cost(self,data,labels):
        data_processed=prepare_for_training(data,self.polynomial_degree,self.sinusoid_degree,self.normalize_data)[0]    # 预处理数据
        return self.cost_function(data_processed,labels)

    def predict(self,data):
        """
        用训练好的参数值得到回归结果
        :param data:
        :return:
        """
        data_processed=prepare_for_training(data,self.polynomial_degree,self.sinusoid_degree,self.normalize_data)[0]    # 预处理数据
        prediction = LinearRegression.hypothesis(data_processed,self.theta)
        return prediction